package com.xm.security.config.security;

import cn.hutool.core.util.StrUtil;
import com.xm.common.utils.StrPool;
import com.xm.security.config.properties.SecurityProperties;
import com.xm.security.exception.ExceptionAdviceHandler;
import com.xm.security.exception.ExceptionFilter;
import com.xm.security.utils.JwtUtil;
import io.jsonwebtoken.JwtException;
import lombok.AllArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.cors.CorsUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author yousj
 * @since 2021/6/12
 */
@AllArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private JwtUtil jwtUtil;
    private UserDetailsService userServiceImpl;
    private ExceptionAdviceHandler exceptionAdviceHandler;
    private ExceptionFilter exceptionFilter;
    private SecurityProperties securityProperties;

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 测试使用内存用户
        // auth.inMemoryAuthentication().withUser("admin").password("$2a$10$ksnqlsciykvr1nwHXlWHceoAcnmfF/1sEoOYudQH4cfIWwdUvmSqG").roles();
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {

        http
                // 关闭cors, csrf
                .cors().and().csrf().disable()
                // 关闭headers缓存
                .headers().cacheControl().disable()
                .and()
                .formLogin()
                .and()
                .authorizeRequests()
                // 放行资源
                .requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
                .antMatchers(securityProperties.getUnAuthPermitUrls()).permitAll()
                // 其余资源走自定义权限认证
                .anyRequest().access("@securityConfig.hasPermission(request)")
                .and()
                // 禁用session, 使用token方式认证
                .sessionManagement().sessionFixation().migrateSession().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                // 自定义登录认证逻辑
                .userDetailsService(userServiceImpl)
                // 由实现类统一处理异常
                .addFilterBefore(exceptionFilter, UsernamePasswordAuthenticationFilter.class)
                // 自定义认证异常处理
                .exceptionHandling()
                // 已认证用户权限认证异常处理
                .accessDeniedHandler(((req, res, e) -> exceptionAdviceHandler.accessDeniedHandler(e, res)))
                // 未认证用户权限认证异常处理
                .authenticationEntryPoint(((req, res, e) -> exceptionAdviceHandler.authenticationEntryPointHandler(e, res)));

    }

    public boolean hasPermission(HttpServletRequest request) {
        List<String> urls = getDetails(request, paresJwtToken(request));
        return Arrays.stream(securityProperties.getAuthPermitUrls()).anyMatch(url -> new AntPathRequestMatcher(url).matches(request))
                || urls.stream().anyMatch(url -> new AntPathRequestMatcher(url).matches(request));
    }

    private String paresJwtToken(HttpServletRequest request) {
        String jwtToken = request.getHeader(securityProperties.getTokenName());
        if (StrUtil.isAllBlank(jwtToken)) throw new JwtException(StrPool.EMPTY);
        return jwtUtil.paresJwtToken(jwtToken);
    }

    private List<String> getDetails(HttpServletRequest request, String subject) {
        UserDetails userDetails = userServiceImpl.loadUserByUsername(subject);
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        SecurityContextHolder.getContext().setAuthentication(authentication);
        return userDetails.getAuthorities()
                .stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList());
    }

}
